Merged
Conversation
Add DraftInterface/DraftFactoryInterface abstraction and wire it into GeneratorConfiguration. The pipeline does not yet use the draft — this phase establishes the structural foundation only. New classes: - DraftInterface: getDefinition(): DraftBuilder - DraftFactoryInterface: getDraftForSchema(JsonSchema): DraftInterface - DraftBuilder: fluent builder for Draft instances - Draft: built result with getCoveredTypes(string|array) — always includes 'any' - Element/Type: holds a type name and its ModifierInterface list - Modifier/ModifierInterface: modify() contract for future phases - Draft_07: registers all 7 JSON Schema types + 'any' (empty modifier lists) - AutoDetectionDraft: DraftFactoryInterface that detects $schema keyword, falls back to Draft_07 for absent/unrecognised values GeneratorConfiguration gains getDraft()/setDraft() accepting DraftInterface|DraftFactoryInterface; defaults to AutoDetectionDraft. phpcs.xml: exclude Squiz.Classes.ValidClassName.NotPascalCase to allow underscore-separated draft class names (Draft_07, Draft_2020_12, etc.).
…raft architecture Phase 2 — Eliminate PropertyMetaDataCollection: - Add bool $required parameter to PropertyFactory::create, ProcessorFactoryInterface::getProcessor and all processor constructors - Replace isAttributeRequired() lookups with $this->required throughout - Move addDependencyValidator to BaseProcessor::addPropertiesToSchema, reading directly from $json['dependencies'][$propertyName] - Update SchemaDefinition::resolveReference to accept bool $required instead of PropertyMetaDataCollection; simplify cache key accordingly - Remove PMC save/restore from AdditionalPropertiesValidator, PatternPropertiesValidator, ArrayTupleValidator, AbstractComposedValueProcessor, and IfProcessor - Delete PropertyMetaDataCollection entirely Phase 3 refinements — Draft architecture: - DraftInterface.getDefinition() returns DraftBuilder (not Draft), enabling draft extension by consumers; PropertyFactory builds and caches - Type constructor auto-installs TypeCheckModifier via TypeConverter::jsonSchemaToPhp, removing per-type boilerplate from Draft_07 - DefaultValueModifier is self-contained: reads type from schema, resolves is_* check and int->float coercion internally; no callable constructor API - Draft_07 is now a clean declarative list with no implementation detail - Replace DRAFT_BYPASS_TYPES hardcoded list with Draft::hasType() check - Add TypeConverter::jsonSchemaToPhp() to centralise JSON->PHP type mapping
…to validator factories Introduce AbstractValidatorFactory hierarchy (SimplePropertyValidatorFactory, SimpleBaseValidatorFactory) and migrate all keyword-specific validator generation out of the legacy processors into dedicated factory classes registered on Draft_07. Migrated types: string (pattern, minLength, maxLength, format), integer/number (minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf), array (items, minItems, maxItems, uniqueItems, contains), universal (enum, filter). Also adds DefaultArrayToEmptyArrayModifier for the array default-empty behaviour. Legacy processor generateValidators overrides for string, numeric, and array types are removed entirely. AbstractNumericProcessor is now empty (flattened into IntegerProcessor/NumberProcessor directly extending AbstractTypedValueProcessor). PropertyFactory exposes applyTypeModifiers/applyUniversalModifiers as public methods; MultiTypeProcessor uses these directly instead of a skipUniversalModifiers flag, eliminating the 'type-only' sentinel. FilterValidator.validateFilterCompatibilityWithBaseType now derives the effective type from getNestedSchema() for object properties instead of relying on a set/restore workaround in FilterValidatorFactory.
Multi-type properties ("type": [...]) are now handled directly in
PropertyFactory::createMultiTypeProperty instead of delegating to
MultiTypeProcessor. Each type is processed through its legacy single-type
processor and Draft type modifiers; type-check validators are collected and
consolidated into a single MultiTypeCheckValidator; decorators are forwarded
via PropertyTransferDecorator; universal modifiers run once on the main
property after all sub-properties resolve.
- Delete MultiTypeProcessor
- PropertyProcessorFactory::getProcessor now only handles string types;
the private getSingleTypePropertyProcessor wrapper is inlined
- ProcessorFactoryInterface::getProcessor parameter narrowed to string
- Type validity is checked via PropertyFactory::checkType (shared between
scalar and multi-type paths)
- Three invalidRecursiveMultiType test expectations updated: outer
InvalidItemException property name changes from "item of array property"
to "property" due to different extracted method registration order
…r architecture - Convert MinProperties, MaxProperties, PropertyNames, PatternProperties, AdditionalProperties, and Properties processing from BaseProcessor methods into validator factories (Factory/Object/) registered via addValidator() in Draft_07, consistent with the scalar/array/number/string factory pattern - ObjectModifier remains a proper modifier (structural, not keyword-driven) - PropertyFactory: add type=object path that calls processSchema directly, wires the outer property via TypeCheckModifier + ObjectModifier, and runs universal modifiers (filter/enum/default) on the outer property - PropertyFactory: add RequiredPropertyValidator for required type=object properties; strip property-level keywords before passing schema to processSchema to prevent double-application on the nested class root - SchemaProcessor: add transferComposedPropertiesToSchema (migrated from BaseProcessor) with correct use imports so allOf/anyOf/oneOf branch properties are transferred and conflict detection fires correctly - BaseProcessor: remove all methods now handled by Draft modifiers/factories
…dValueProcessorFactory Introduce Model\Validator\Factory\Composition\AbstractCompositionValidatorFactory (extends AbstractValidatorFactory) with shared composition helpers, plus five concrete factories: AllOfValidatorFactory, AnyOfValidatorFactory, OneOfValidatorFactory, NotValidatorFactory, IfValidatorFactory. A marker interface ComposedPropertiesValidatorFactoryInterface replaces ComposedPropertiesInterface for the property-transfer guard. Register all five on the 'any' type in Draft_07 via addValidator(), consistent with the keyword-keyed pattern established in Phase 4. Delete ComposedValueProcessorFactory and all legacy ComposedValue processor classes (AbstractComposedValueProcessor, AllOfProcessor, AnyOfProcessor, OneOfProcessor, NotProcessor, IfProcessor and their interfaces). AbstractPropertyProcessor is slimmed to only the RequiredPropertyValidator and isImplicitNullAllowed helpers still needed by the bridge. Update all is_a() checks and use-imports in Schema, BaseProcessor, SchemaProcessor, ConditionalPropertyValidator, and CompositionRequiredPromotionPostProcessor to reference the new factory classes.
…ConstModifier, IntToFloatModifier, NullModifier - Delete all PropertyProcessor/Property/* classes (AbstractPropertyProcessor, AbstractValueProcessor, AbstractTypedValueProcessor, AbstractNumericProcessor, StringProcessor, IntegerProcessor, NumberProcessor, BooleanProcessor, ArrayProcessor, ObjectProcessor, NullProcessor, AnyProcessor, ConstProcessor, ReferenceProcessor, BasereferenceProcessor, BaseProcessor) - Delete PropertyProcessorFactory and ProcessorFactoryInterface - Remove ProcessorFactoryInterface constructor parameter from PropertyFactory - Inline $ref / baseReference routing as private methods on PropertyFactory - Refactor PropertyFactory::create into focused dispatch + four extracted methods (createObjectProperty, createBaseProperty, createTypedProperty, buildProperty) with a single unified applyModifiers helper replacing three separate methods - Add ConstModifier (registered on 'any' type): sets PropertyType from const value type and adds InvalidConstException validator; immutability is fully respected - Add IntToFloatModifier (registered on 'number' type): adds IntToFloatCastDecorator - Add NullModifier (registered on 'null' type): clears PropertyType and adds TypeHintDecorator - Register object type in Draft_07 with typeCheck=false (PropertyFactory handles object type-check via wireObjectProperty, not via Draft modifier dispatch) - Remove stale PropertyProcessorFactory imports from validator/schema files - Add ConstPropertyTest::testConstPropertyHasNoSetterWhenImmutable - Add docs/source/generator/custom/customDraft.rst: custom draft / modifier guide - Update setDraft documentation in gettingStarted.rst with seealso link - Update CLAUDE.md architecture section to document the final Draft modifier system
Coverage Report for CI Build 24573811184Coverage decreased (-0.1%) to 98.551%Details
Uncovered Changes
Coverage Regressions1 previously-covered line in 1 file lost coverage.
Coverage Stats
💛 - Coveralls |
…e to safely rely on them to be present
…t keys (#103) - SerializationPostProcessor: key skipNotProvidedPropertiesMap by schema name (getName()) rather than the PHP attribute name (getAttribute(true)) so the array_diff comparison against rawModelDataInput works correctly - SerializableTrait: use #[SchemaName] reflection to map PHP property names to their original JSON Schema names; fix getCustomSerializerMethod cache to be per-class (static::class key) and use array_key_exists instead of isset to correctly handle the false sentinel; remove str_starts_with('_') fallback — #[Internal] is now the sole exclusion mechanism - Add Internal attribute to production library; mark all three static trait properties with #[Internal] - Update evaluateAttribute: use jsonSerialize/toArray with correct depth propagation; cache serialization capability per class (not per call); handle depth-exhausted path after capability cache to avoid poisoning - $except now takes schema names only (breaking change) - Tests: add Issue103Test (4 combined tests), extend PhpAttributeTest with leading-digit schema name case, fix stale testBuiltinAttributes assertion Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…enerated internal properties
Previously, properties generated as internal (additional properties, pattern
properties, rawModelDataInput, errorRegistry, etc.) used a leading underscore
as both a naming convention and a serialization exclusion signal. This was an
implicit contract that polluted the namespace and conflicted with clean PHP
naming.
Changes:
- AbstractProperty::getAttribute(): no longer prepends '_' for internal properties
- Property::setInternal(true): auto-adds #[Internal] PHP attribute to the
property; the flag is one-way (calling setInternal(false) after true is a
no-op since the attribute cannot be removed from the list)
- RenderJob::getUseForSchema(): always includes Internal::class in the use list
so the hardcoded rawModelDataInput/errorRegistry declarations in Model.phptpl
can carry the #[Internal] annotation
- Model.phptpl: rawModelDataInput and errorRegistry declared with #[Internal];
all $this->_* references updated throughout
- BuilderClass.phptpl: rawModelDataInput and errorRegistry renamed
- All validator and post-processor templates updated (errorRegistry,
additionalProperties, patternProperties, patternPropertiesMap,
propertyValidationState, skipNotProvidedPropertiesMap, rawModelDataInput)
- PHP source files updated (RenderHelper, validator factories,
post-processors) where property names were emitted as strings
- SerializableTrait fallback path: removed str_starts_with('_') check —
#[Internal] is now the sole mechanism for excluding properties in the
fallback (non-generated) serialization path
- Test: fix Issue116Test to reference 'additionalProperties' (not
'_additionalProperties') when reflecting on generated class
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This reverts commit ef5550c.
…nd filter tests - Type::addValidator now stores validators under their key in the modifiers array, so a second addValidator call with the same key replaces the existing entry - Add DraftExtensibilityTest covering custom validator override, chained validators, and new type registration - Add PassThroughTypeCheckValidatorTest - Add object-level composition (allOf/not) schema fixtures and test coverage - Add PropertyNamesValidator and PropertyNamesTest coverage - Add test confirming filters with empty acceptedTypes are compatible with any property type - CLAUDE.md: add clarification policy, test consolidation rules, and other project guidance Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…erValidator - Rename TypeConverter::jsonSchemaToPhp to jsonSchemaToPHP and update all call sites - Replace mapDataTypes wrapper in FilterValidator with direct array_map(TypeConverter::jsonSchemaToPHP(...), ...) - Use constructor property promotion for $transformingFilter - Refactor validateFilterCompatibilityWithBaseType into runCompatibilityCheck, which handles both typed properties (existing logic) and untyped properties (new: throws SchemaException when the filter does not cover every PHP primitive type) - Update constructor guard: typed/nested/transforming-filter cases check immediately; untyped + non-transforming filter defers to the upcoming FilterCompatibilityPostProcessor - Add public validateCompatibilityWithProperty for the post-processor to call - Guard typeCheck template value against 'mixed' in acceptedTypes (no PHP runtime check) - Patch validateFilterCompatibilityWithTransformedType to handle null/mixed return types from transforming filters (unconstrained output requires the subsequent filter to accept all types) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The previous check threw SchemaException on any partial type mismatch. The new rule: only throw when a typed property has zero overlap with the filter's acceptedTypes (the filter can never execute). Partial overlap is valid — the runtime typeCheck guard in the generated code already skips the filter for non-matching value types. - Untyped properties are always OK: any non-empty acceptedTypes has overlap with the infinite type space - filter accepts all types ([] or 'mixed'): always OK - Typed property, zero overlap: SchemaException - Typed property, partial or full overlap: OK Simplifications enabled by this design: - No post-processor needed (FilterCompatibilityPostProcessor dropped) - No deferred check path ($compatibilityChecked flag removed) - No validateCompatibilityWithProperty public method - runCompatibilityCheck reduced to an intersection check Update existing tests to reflect the new behaviour: - testFilterWhichAppliesToMultiTypePropertyPartiallyThrowsAnException renamed and converted to a success-path test (partial overlap is now OK) - testRestrictedFilterOnUntypedPropertyIsAllowed documents that untyped properties with restricted filters are valid Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mbination Add 'mixed' to the valid acceptedTypes values in GeneratorConfiguration::addFilter. Also enforce that 'mixed' must not be combined with other types, since that would be contradictory — throw InvalidFilterException if the combination is detected. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The Filter.phptpl template now uses && short-circuit so the filter callable is only invoked when the runtime value type is in the filter's acceptedTypes list. Non-matching types silently skip the filter. The typeCheck expression is changed from negative (throw path) to positive (is_type($value) || ...) joined with ||, so the && condition passes only for matching types. Tests updated: renamed/added cases to assert generation succeeds for partial-overlap and untyped-property scenarios, and to verify runtime filter-skip behaviour for non-matching types. Also fixes stray `new \DateTimeZone()` line in TypeConverter and the `end(explode(...))` PHP notice in FilterValidator. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rst parameter FilterInterface::getAcceptedTypes() is removed (see production repo commit). Accepted types are now derived automatically from the first parameter's type hint of the filter callable via FilterValidator::getAcceptedTypes() (private, non-static). - No type hint or mixed parameter → [] (accepts all, no type guard) - ReflectionNamedType → [name] plus 'null' if allowsNull() - ReflectionUnionType → all constituent type names The mixed+other-types guard is dropped: PHP's type system makes mixed|OtherType a parse error so the check was unreachable via reflection. FilterProcessor restructured to a single instanceof check per iteration: validation, type wiring, addValidator, and pass-through all happen in one block; addTransformedValuePassThrough runs after addValidator so the transforming filter's own FilterValidator also receives the check. Tests updated: helpers drop acceptedTypes parameter; callables carry their own type hints; the removed invalid-type-name test is deleted. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace FilterInterface::getAcceptedTypes() with reflection on the callable's first parameter type hint (FilterReflection utility). Filters with no type hint now throw SchemaException at generation time; mixed accepts all types with no runtime guard. Extract TypeCheck utility for building is_type/instanceof expressions used in generated code. Introduce TransformingFilterOutputTypePostProcessor (always-on internal post-processor) that computes the output type for properties with a transforming filter after composition has fully resolved the base type. FilterProcessor eagerly applies the output type when the base type is already known at filter-processing time; the post-processor handles the deferred case where the type comes from a sibling allOf branch. PassThroughTypeCheckValidator updated to accept string[] instead of a ReflectionType and now deduplicates its type list. Dead $isBuiltin parameter and unused fromReflectionType factory removed from ReflectionTypeCheckValidator. CLAUDE.md: add rule that filter callables may only reference production-library classes (generator-package callables would be embedded in generated code and fail at runtime without the generator installed). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Phase 4d tests for output type formula, reflection-based accepted types, and filter chain behaviour (R2, R6, R7, F3-F7, F4, CH2) - Change FilterReflection to throw InvalidFilterException (not SchemaException) for missing/void/never return types and missing parameter type hints, since the error is in the filter definition rather than the schema; add tests F5-F7 - Add SingleFileProvider with tests covering happy path, invalid input, getBaseDirectory(), and cross-file $ref resolution - Fix SingleFileProvider constructor to avoid TypeError when json_decode returns null on invalid JSON; suppress file_get_contents warning for missing files - Replace all inline JSON schemas in FilterTest with dedicated schema files under tests/Schema/FilterTest/ to follow the established test suite pattern - Update filter.rst: rewrite overlap/skip behaviour, remove IncompatibleFilterException section, update custom filter examples to use type hints instead of getAcceptedTypes() Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.